<?php

namespace iflow\swoole\implement\Server\Proxy;

use Swoole\Table;
use Swoole\Server;
use iflow\Utils\Tools\Timer;
use Swoole\Coroutine\Channel;
use iflow\swoole\implement\Server\Tcp\Service as TCPService;
use iflow\swoole\implement\Server\Proxy\implement\Events\Event;

class Service extends TCPService {

    protected string $defaultEventClass = Event::class;

    protected Table $table;

    protected Channel $serverChannel;

    protected int $consumptionTimer = 0;

    protected function createServiceAfter(): void {
        parent::createServiceAfter(); // TODO: Change the autogenerated stub

        $this -> table = $this -> tables -> createTable('proxy_raw_table', [
            'fields' => [
                [ 'name' => 'tunnel_fd', 'type' => Table::TYPE_INT ],
                [ 'name' => 'remote_fd', 'type' => Table::TYPE_INT ]
            ]
        ]);
        $this->events['task'] = [ $this, 'onCoroutineTask' ];
    }

    public function onCoroutineTask(Server $server, Server\Task $task): void {
         go(function () use ($server, $task) {
             $data = $task -> data;

             if ($tunnel = $this->table -> get('proxy-server')) {
                 $this -> serverChannel -> push($data['data']);
                 $tunnelResult = $this -> table -> get($data['fd']) ?: [];
                 $tunnelSig = $tunnelResult['tunnel_fd'] ?? 0;

                 // 验证 TUNNEL-SERVER 是否可用
                 if ($tunnelSig === 0 || !$this -> checkClientConnection($tunnelSig)) {
                     $this -> table -> set($data['fd'], [ 'tunnel_fd' => 0, 'remote_fd' => $data['fd'] ]);
                     $server -> send($tunnel['tunnel_fd'], $data['fd']);
                 }
             }
         });
    }

    public function onWorkerStart() {
        $this -> serverChannel = new Channel($this -> config -> get('channel@size'));
        $this -> consumption();
    }

    protected function consumption(): void {
        $this -> consumptionTimer = Timer::tick(10, function () {
            if (!$this -> serverChannel -> isEmpty()) {
                $data = $this -> serverChannel -> pop();
                $raw = $this -> table -> get($data['remote_fd']);

                if (!$raw || !$raw['tunnel_fd']) {
                    $this->serverChannel -> push($data);
                    return;
                }

                $this -> getSwService() -> send($raw['tunnel_fd'], $data['body']);
            }
        });
    }

    /**
     * @return Table
     */
    public function getTable(): Table {
        return $this->table;
    }

    public function send(string $pack): void {
        $this -> getSwService() -> send($pack . $this -> config -> getPackageEof());
    }

    public function checkTunnelConnection(Server $server, int $fd, string $data): bool {
        $remoteId = intval($data);

        if ($raw = $this -> table -> get($remoteId)) {
            $raw['tunnel_fd'] = $fd;
            $this -> table -> set($remoteId, $raw);
            return true;
        }

        foreach ($this -> table as $result) {
            if ($result['tunnel_fd'] === $fd) $server -> send($result['remote_fd'], $data);
        }
        return true;
    }

}
